package com.opennews.openplatform.familyexpenseapi.service

import com.opennews.openplatform.familyexpenseapi.entity.Tables.REQUEST_MAP
import com.opennews.openplatform.familyexpenseapi.entity.tables.records.RequestMapRecord
import com.opennews.openplatform.familyexpenseapi.jooq.DSLContextWrapper
import com.opennews.openplatform.familyexpenseapi.jooq.RecordHelper
import com.opennews.openplatform.myspringbootcore.common.constant.ROLE_ACCOUNT_GROUP_ADMIN
import com.opennews.openplatform.myspringbootcore.common.constant.ROLE_ACCOUNT_GROUP_USER
import com.opennews.openplatform.myspringbootcore.security.RequestMapManager
import org.jooq.DSLContext
import org.springframework.http.HttpMethod
import org.springframework.stereotype.Service

@Service
class RequestMapService(
    private val dslContext: DSLContext,
    private val dslContextWrapper: DSLContextWrapper,
    private val requestMapManager: RequestMapManager,
) {
    /**
     * Prepares the initial data for Role.
     */
    fun init() {
        if (!dslContextWrapper.exists(REQUEST_MAP)) {
            val records = mutableListOf<RequestMapRecord>()

            records.apply {
                add(createRecordForAll("/app-error-report/**", null))
                add(createRecordForAll("/authentication/**", null))

                add(createRecordForAuthenticated("/map-location/**", null))
                add(createRecordForAuthenticated("/file/**", null))
                add(createRecordForAuthenticated("/join-account-group-request/**", null))
                add(createRecordForAuthenticated("/user/**", null))
                add(createRecordForAuthenticated("/user-active-token/**", null))
                add(createRecordForAuthenticated("/app-release-log/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/account-group/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/account-group-user/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/bank/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/bank-card/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/data-initialization/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/income-expense-analysis/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/income-expense-detail/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/income-expense-detail-type/query-by-account-group", HttpMethod.GET.name()))
                add(
                    createRecordForAuthenticated(
                        "/income-expense-detail-type/query-analysis-not-included-detail-types",
                        HttpMethod.GET.name()
                    )
                )
                add(createRecordForAuthenticated("/journey/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/journey-income-expense-detail/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/region/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/token-check/**", HttpMethod.GET.name()))
                add(createRecordForAuthenticated("/authentication/unregister", HttpMethod.POST.name()))
                add(createRecordForAuthenticated("/account-group-user/quit", HttpMethod.POST.name()))

                add(createRecordForUserAndAdmin("/income-expense-detail/**", HttpMethod.POST.name()))
                add(createRecordForUserAndAdmin("/journey/**", HttpMethod.POST.name()))
                add(createRecordForUserAndAdmin("/journey-income-expense-detail/**", HttpMethod.POST.name()))

                add(createForAdmin("/income-expense-detail-type/check-existing", HttpMethod.GET.name()))
                add(createForAdmin("/account-group/**", HttpMethod.POST.name()))
                add(createForAdmin("/account-group-user/**", HttpMethod.POST.name()))
                add(createForAdmin("/bank-card/**", HttpMethod.POST.name()))
                add(createForAdmin("/file/upload-avatar", HttpMethod.POST.name()))
                add(createForAdmin("/income-expense-detail-type/**", HttpMethod.POST.name()))
                add(createForAdmin("/join-account-group-request/send", HttpMethod.POST.name()))
                add(createForAdmin("/role/query-non-system-level-roles", HttpMethod.POST.name()))
                add(createForAdmin("/user/query-users-not-in-account-group", HttpMethod.GET.name()))
                add(createForAdmin("/user/add", HttpMethod.POST.name()))
                add(createForAdmin("/user/update-password", HttpMethod.POST.name()))
                add(createForAdmin("/user/update-enabled", HttpMethod.POST.name()))
                add(createForAdmin("/user/delete", HttpMethod.POST.name()))
            }

            dslContext.batchInsert(records).execute()
        }
    }

    /**
     * Extracts role names from request map config attribute.
     * Example: hasRole('admin'), hasAnyRole('reporter', 'planner').
     *
     * @param configAttribute: Property configAttribute of request map.
     * @return List of role names.
     */
    fun extractRoleNames(configAttribute: String): List<String> {
        val roleNames = mutableListOf<String>()

        // This would match the role names, like 'admin' or 'reporter', 'planner'.
        // Regular expression with string concatenation for the prefixes.
        val regex = "(?:" +
                RequestMapManager.HAS_ROLE_PREFIX + "|" +
                RequestMapManager.HAS_ANY_ROLE_PREFIX + "|" +
                RequestMapManager.HAS_NO_ROLE_PREFIX + "|" +
                RequestMapManager.HAS_NO_ANY_ROLE_PREFIX +
                ")\\((.+)\\)"

        // Create a Pattern object
        val pattern = Regex(regex)

        // Find match
        val matchResult = pattern.find(configAttribute)

        // If match found
        matchResult?.let { result ->
            // Get the matched roles string (inside the parentheses)
            var matchedRoles = result.groupValues[1]

            // Remove all single quotes and double quotes
            matchedRoles = matchedRoles.replace("'", "").replace("\"", "")

            // Split by comma
            val splitedRoles = matchedRoles.split(",")

            // Trim each role and add it to the roleNames list
            splitedRoles.forEach { role ->
                roleNames.add(role.trim())
            }
        }

        return roleNames
    }

    /**
     * Checks if security request map configAttribute contains any role in roleNames.
     *
     * @param roleNames: List of role names.
     * @param configAttribute: String of config attribute. Example: hasAnyRole('ROLE_ACCOUNT_GROUP_USER','ROLE_ACCOUNT_GROUP_ADMIN')
     */
    fun containsAnyRoleInConfigAttribute(roleNames: List<String>, configAttribute: String): Boolean {
        return containsAnyRoleInConfigRoleNames(roleNames, extractRoleNames(configAttribute))
    }

    private fun createRecordForAll(url: String, httpMethod: String??): RequestMapRecord {
        return createRecord(RequestMapManager.PERMIT_ALL, url, httpMethod)
    }

    private fun createRecordForAuthenticated(url: String, httpMethod: String?): RequestMapRecord {
        return createRecord(RequestMapManager.IS_AUTHENTICATED, url, httpMethod)
    }

    private fun createRecordForUserAndAdmin(url: String, httpMethod: String?): RequestMapRecord {
        return createRecord(
            requestMapManager.hasAnyRole(
                listOf(ROLE_ACCOUNT_GROUP_ADMIN, ROLE_ACCOUNT_GROUP_USER)
            ), url, httpMethod
        )
    }

    private fun createForAdmin(url: String, httpMethod: String?): RequestMapRecord {
        return createRecord(requestMapManager.hasRole(ROLE_ACCOUNT_GROUP_ADMIN), url, httpMethod)
    }

    private fun createRecord(configAtribute: String, url: String, httpMethod: String??): RequestMapRecord {
        return dslContext.newRecord(REQUEST_MAP).apply {
            RecordHelper.setCommonFields(this)
            this.configAttribute = configAtribute
            this.url = url
            this.httpMethod = httpMethod
        }
    }

    /**
     * Checks if configuredRoleNames contains any role in roleNames.
     *
     * @param roleNames:       List of role names.
     * @param configRoleNames: List of configured role names.
     * @return True means condition meets. False means NOT.
     */
    private fun containsAnyRoleInConfigRoleNames(roleNames: List<String>, configRoleNames: List<String>): Boolean {
        for (roleName in roleNames) {
            for (configRoleName in configRoleNames) {
                if (roleName.equals(configRoleName, ignoreCase = true)) {
                    return true
                }
            }
        }

        return false
    }
}